#include <inc/fs.h>
#include <inc/string.h>
#include <inc/lib.h>

#define debug 0

union Fsipc fsipcbuf __attribute__((aligned(PGSIZE)));

// Send an inter-environment request to the file server, and wait for
// a reply.  The request body should be in fsipcbuf, and parts of the
// response may be written back to fsipcbuf.
// type: request code, passed as the simple integer IPC value.
// dstva: virtual address at which to receive reply page, 0 if none.
// Returns result from the file server.
static int
fsipc(unsigned type, void *dstva) {
    static envid_t fsenv;
    if (fsenv == 0)
        fsenv = ipc_find_env(ENV_TYPE_FS);

    static_assert(sizeof(fsipcbuf) == PGSIZE);

    if (debug)
        cprintf("[%08x] fsipc %d %08x\n", thisenv->env_id, type, *(uint32_t *) &fsipcbuf);

    ipc_send(fsenv, type, &fsipcbuf, PTE_P | PTE_W | PTE_U);
    return ipc_recv(NULL, dstva, NULL);
}

static int devfile_flush(struct Fd *fd);

static ssize_t devfile_read(struct Fd *fd, void *buf, size_t n);

static ssize_t devfile_write(struct Fd *fd, const void *buf, size_t n);

static int devfile_stat(struct Fd *fd, struct Stat *stat);

static int devfile_trunc(struct Fd *fd, off_t newsize);

struct Dev devfile =
        {
                .dev_id =    'f',
                .dev_name =    "file",
                .dev_read =    devfile_read,
                .dev_close =    devfile_flush,
                .dev_stat =    devfile_stat,
                .dev_write =    devfile_write,
                .dev_trunc =    devfile_trunc
        };

// Open a file (or directory).
//
// Returns:
// 	The file descriptor index on success
// 	-E_BAD_PATH if the path is too long (>= MAXPATHLEN)
// 	< 0 for other errors.
int
open(const char *path, int mode) {
    // Find an unused file descriptor page using fd_alloc.
    // Then send a file-open request to the file server.
    // Include 'path' and 'omode' in request,
    // and map the returned file descriptor page
    // at the appropriate fd address.
    // FSREQ_OPEN returns 0 on success, < 0 on failure.
    //
    // (fd_alloc does not allocate a page, it just returns an
    // unused fd address.  Do you need to allocate a page?)
    //
    // Return the file descriptor index.
    // If any step after fd_alloc fails, use fd_close to free the
    // file descriptor.

    int r;
    struct Fd *fd;

    if (strlen(path) >= MAXPATHLEN)
        return -E_BAD_PATH;

    if ((r = fd_alloc(&fd)) < 0)
        return r;

    strcpy(fsipcbuf.open.req_path, path);
    fsipcbuf.open.req_omode = mode;

    if ((r = fsipc(FSREQ_OPEN, fd)) < 0) {
        fd_close(fd, 0);
        return r;
    }

    return fd2num(fd);
}

// Flush the file descriptor.  After this the fileid is invalid.
//
// This function is called by fd_close.  fd_close will take care of
// unmapping the FD page from this environment.  Since the server uses
// the reference counts on the FD pages to detect which files are
// open, unmapping it is enough to free up server-side resources.
// Other than that, we just have to make sure our changes are flushed
// to disk.
static int
devfile_flush(struct Fd *fd) {
    fsipcbuf.flush.req_fileid = fd->fd_file.id;
    return fsipc(FSREQ_FLUSH, NULL);
}

// Read at most 'n' bytes from 'fd' at the current position into 'buf'.
//
// Returns:
// 	The number of bytes successfully read.
// 	< 0 on error.
static ssize_t
devfile_read(struct Fd *fd, void *buf, size_t n) {
    // Make an FSREQ_READ request to the file system server after
    // filling fsipcbuf.read with the request arguments.  The
    // bytes read will be written back to fsipcbuf by the file
    // system server.
    int r;

    fsipcbuf.read.req_fileid = fd->fd_file.id;
    fsipcbuf.read.req_n = n;
    if ((r = fsipc(FSREQ_READ, NULL)) < 0)
        return r;
    assert(r <= n);
    assert(r <= PGSIZE);
    memmove(buf, fsipcbuf.readRet.ret_buf, r);
    return r;
}


// Write at most 'n' bytes from 'buf' to 'fd' at the current seek position.
//
// Returns:
//	 The number of bytes successfully written.
//	 < 0 on error.

static ssize_t
devfile_write(struct Fd *fd, const void *buf, size_t n) {
    // Make an FSREQ_WRITE request to the file system server.  Be
    // careful: fsipcbuf.write.req_buf is only so large, but
    // remember that write is always allowed to write *fewer*
    // bytes than requested.
    // LAB 5: Your code here
    int r;
    struct Fsreq_write *fw = &fsipcbuf.write;
    fw->req_fileid = fd->fd_file.id;
    fw->req_n = MIN(4088, n);
    memmove(fw->req_buf, buf, fw->req_n);
    return fsipc(FSREQ_WRITE, NULL);
}

static int
devfile_stat(struct Fd *fd, struct Stat *st) {
    int r;

    fsipcbuf.stat.req_fileid = fd->fd_file.id;
    if ((r = fsipc(FSREQ_STAT, NULL)) < 0)
        return r;
    strcpy(st->st_name, fsipcbuf.statRet.ret_name);
    st->st_size = fsipcbuf.statRet.ret_size;
    st->st_isdir = fsipcbuf.statRet.ret_isdir;
    return 0;
}

// Truncate or extend an open file to 'size' bytes
static int
devfile_trunc(struct Fd *fd, off_t newsize) {
    fsipcbuf.set_size.req_fileid = fd->fd_file.id;
    fsipcbuf.set_size.req_size = newsize;
    return fsipc(FSREQ_SET_SIZE, NULL);
}


// Synchronize disk with buffer cache
int
sync(void) {
    // Ask the file server to update the disk
    // by writing any dirty blocks in the buffer cache.

    return fsipc(FSREQ_SYNC, NULL);
}

